#include "Model/DailyCalendarRule.hpp"
#include "CalendarRuleTests.hpp"
#include "interval_tests.hpp"
#include <boost/test/auto_unit_test.hpp>
#include <vector>


namespace 
{

	using namespace Model;

	class DailyCalendarRuleTestFixture
	{
	public:
		DailyCalendarRuleTestFixture()
		{

			day1Times_.push_back( 100000);
			day1Times_.push_back( 110000);
			day1Times_.push_back( 120000);
			day1Times_.push_back( 130000);
			day1Times_.push_back( 140000);
			day1Times_.push_back( 150000);
			day1Times_.push_back( 160000);
			day1Times_.push_back( 170000);
			day1Times_.push_back( 180000);

			day3Times_ = day2Times_ = day1Times_;

			Calendar::Date day1( 2008, 12, 11);
			day1Dates_.push_back( DailyCalendarRule::calculateTime( day1, day1Times_[0]));
			day1Dates_.push_back( DailyCalendarRule::calculateTime( day1, day1Times_[1]));
			day1Dates_.push_back( DailyCalendarRule::calculateTime( day1, day1Times_[2]));
			day1Dates_.push_back( DailyCalendarRule::calculateTime( day1, day1Times_[3]));
			day1Dates_.push_back( DailyCalendarRule::calculateTime( day1, day1Times_[4]));
			day1Dates_.push_back( DailyCalendarRule::calculateTime( day1, day1Times_[5]));
			day1Dates_.push_back( DailyCalendarRule::calculateTime( day1, day1Times_[6]));
			day1Dates_.push_back( DailyCalendarRule::calculateTime( day1, day1Times_[7]));
			day1Dates_.push_back( DailyCalendarRule::calculateTime( day1, day1Times_[8]));

			Calendar::Date day2( 2008, 12, 12);
			day2Dates_.push_back( DailyCalendarRule::calculateTime( day2, day2Times_[0]));
			day2Dates_.push_back( DailyCalendarRule::calculateTime( day2, day2Times_[1]));
			day2Dates_.push_back( DailyCalendarRule::calculateTime( day2, day2Times_[2]));
			day2Dates_.push_back( DailyCalendarRule::calculateTime( day2, day2Times_[3]));
			day2Dates_.push_back( DailyCalendarRule::calculateTime( day2, day2Times_[4]));
			day2Dates_.push_back( DailyCalendarRule::calculateTime( day2, day2Times_[5]));
			day2Dates_.push_back( DailyCalendarRule::calculateTime( day2, day2Times_[6]));
			day2Dates_.push_back( DailyCalendarRule::calculateTime( day2, day2Times_[7]));
			day2Dates_.push_back( DailyCalendarRule::calculateTime( day2, day2Times_[8]));

			Calendar::Date day3( 2008, 12, 13);
			day3Dates_.push_back( DailyCalendarRule::calculateTime( day3, day3Times_[0]));
			day3Dates_.push_back( DailyCalendarRule::calculateTime( day3, day3Times_[1]));
			day3Dates_.push_back( DailyCalendarRule::calculateTime( day3, day3Times_[2]));
			day3Dates_.push_back( DailyCalendarRule::calculateTime( day3, day3Times_[3]));
			day3Dates_.push_back( DailyCalendarRule::calculateTime( day3, day3Times_[4]));
			day3Dates_.push_back( DailyCalendarRule::calculateTime( day3, day3Times_[5]));
			day3Dates_.push_back( DailyCalendarRule::calculateTime( day3, day3Times_[6]));
			day3Dates_.push_back( DailyCalendarRule::calculateTime( day3, day3Times_[7]));
			day3Dates_.push_back( DailyCalendarRule::calculateTime( day3, day3Times_[8]));

			dayEndTime_ = 240000;
			dayBeginTime_ = 0;

		}

		std::vector< unsigned int> day1Times_;
		std::vector< unsigned int> day2Times_;
		std::vector< unsigned int> day3Times_;

		std::vector< Calendar::Time> day1Dates_;
		std::vector< Calendar::Time> day2Dates_;
		std::vector< Calendar::Time> day3Dates_;

		unsigned int dayEndTime_;
		unsigned int dayBeginTime_;
	};

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testMultiDayNoTrimming)
{
	// Daily Rules:
	// |-------day1--------|...|-------day3-------|
	//   1     3   5     7  ...  1     3   5     7
	//   |=====|   |=====|       |=====|   |=====|
	// Calendar range: 
	// 0 [day1]                                   8 [day3]
	// |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|
	DailyCalendarRule rules;
	DailyCalendarRuleTestFixture fixture;

	rules.mergeDailyPeriod( fixture.day1Times_[1], fixture.day1Times_[3]);
	rules.mergeDailyPeriod( fixture.day1Times_[5], fixture.day1Times_[7]);
	Calendar::PeriodSet result;
	rules.getPeriods( fixture.day1Dates_[0], fixture.day3Dates_[8], result);

	BOOST_REQUIRE_EQUAL( 6, result.size());
	Calendar::PeriodSet::const_iterator resI = result.begin();
	CHECK_TIME_INTERVAL( fixture.day1Dates_[1], fixture.day1Dates_[3], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day1Dates_[5], fixture.day1Dates_[7], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day2Dates_[1], fixture.day2Dates_[3], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day2Dates_[5], fixture.day2Dates_[7], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day3Dates_[1], fixture.day3Dates_[3], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day3Dates_[5], fixture.day3Dates_[7], *resI); ++resI;
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testMultiDayWithIntersection)
{
	// Daily Rules:
	// |-------day1--------|...|-------day3-------|
	//   1     3   5     7  ...  1     3   5     7
	//   |=====|   |=====|       |=====|   |=====|
	// Calendar range: 
	//      2 [day1]                          6 [day3]
	//      |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|
	DailyCalendarRule rules;
	DailyCalendarRuleTestFixture fixture;

	rules.mergeDailyPeriod( fixture.day1Times_[1], fixture.day1Times_[3]);
	rules.mergeDailyPeriod( fixture.day1Times_[5], fixture.day1Times_[7]);
	Calendar::PeriodSet result;
	rules.getPeriods( fixture.day1Dates_[2], fixture.day3Dates_[6], result);

	BOOST_REQUIRE_EQUAL( 6, result.size());
	Calendar::PeriodSet::const_iterator resI = result.begin();
	CHECK_TIME_INTERVAL( fixture.day1Dates_[2], fixture.day1Dates_[3], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day1Dates_[5], fixture.day1Dates_[7], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day2Dates_[1], fixture.day2Dates_[3], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day2Dates_[5], fixture.day2Dates_[7], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day3Dates_[1], fixture.day3Dates_[3], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day3Dates_[5], fixture.day3Dates_[6], *resI); ++resI;
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testMultiDayWithBoundary1)
{
	// Daily Rules:
	// |-------day1--------|...|-------day3-------|
	//   1     3   5     7  ...  1     3   5     7
	//   |=====|   |=====|       |=====|   |=====|
	// Calendar range: 
	//   1 [day1]                                7 [day3]
	//   |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|
	DailyCalendarRule rules;
	DailyCalendarRuleTestFixture fixture;

	rules.mergeDailyPeriod( fixture.day1Times_[1], fixture.day1Times_[3]);
	rules.mergeDailyPeriod( fixture.day1Times_[5], fixture.day1Times_[7]);
	Calendar::PeriodSet result;
	rules.getPeriods( fixture.day1Dates_[1], fixture.day3Dates_[7], result);

	BOOST_REQUIRE_EQUAL( 6, result.size());
	Calendar::PeriodSet::const_iterator resI = result.begin();
	CHECK_TIME_INTERVAL( fixture.day1Dates_[1], fixture.day1Dates_[3], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day1Dates_[5], fixture.day1Dates_[7], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day2Dates_[1], fixture.day2Dates_[3], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day2Dates_[5], fixture.day2Dates_[7], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day3Dates_[1], fixture.day3Dates_[3], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day3Dates_[5], fixture.day3Dates_[7], *resI); ++resI;
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testMultiDayWithBoundary2)
{
	// Daily Rules:
	// |-------day1--------|...|-------day3-------|
	//   1     3   5     7  ...  1     3   5     7
	//   |=====|   |=====|       |=====|   |=====|
	// Calendar range: 
	//         3 [day1]                    5 [day3]
	//         |^^^^^^^^^^^^^^^^^^^^^^^^^^^|
	DailyCalendarRule rules;
	DailyCalendarRuleTestFixture fixture;

	rules.mergeDailyPeriod( fixture.day1Times_[1], fixture.day1Times_[3]);
	rules.mergeDailyPeriod( fixture.day1Times_[5], fixture.day1Times_[7]);
	Calendar::PeriodSet result;
	rules.getPeriods( fixture.day1Dates_[3], fixture.day3Dates_[5], result);

	BOOST_REQUIRE_EQUAL( 4, result.size());
	Calendar::PeriodSet::const_iterator resI = result.begin();
	CHECK_TIME_INTERVAL( fixture.day1Dates_[5], fixture.day1Dates_[7], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day2Dates_[1], fixture.day2Dates_[3], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day2Dates_[5], fixture.day2Dates_[7], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day3Dates_[1], fixture.day3Dates_[3], *resI); ++resI;
}


// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testSingleDayWithIntersection)
{
	// Daily Rules:
	// |-------day1--------|
	//   1     3   5     7  
	//   |=====|   |=====|
	// Calendar range: 
	//      2 [day1]  6 [day1]
	//      |^^^^^^^^^|
	DailyCalendarRule rules;
	DailyCalendarRuleTestFixture fixture;

	rules.mergeDailyPeriod( fixture.day1Times_[1], fixture.day1Times_[3]);
	rules.mergeDailyPeriod( fixture.day1Times_[5], fixture.day1Times_[7]);
	Calendar::PeriodSet result;
	rules.getPeriods( fixture.day1Dates_[2], fixture.day1Dates_[6], result);

	BOOST_REQUIRE_EQUAL( 2, result.size());
	Calendar::PeriodSet::const_iterator resI = result.begin();
	CHECK_TIME_INTERVAL( fixture.day1Dates_[2], fixture.day1Dates_[3], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day1Dates_[5], fixture.day1Dates_[6], *resI); ++resI;
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testSingleDayWithElimination)
{
	// Daily Rules:
	// |-------day1-----------|
	//   1     3      6     8  
	//   |=====|      |=====|
	// Calendar range: 
	//           4  5
	//           |^^|
	DailyCalendarRule rules;
	DailyCalendarRuleTestFixture fixture;

	rules.mergeDailyPeriod( fixture.day1Times_[1], fixture.day1Times_[3]);
	rules.mergeDailyPeriod( fixture.day1Times_[6], fixture.day1Times_[8]);
	Calendar::PeriodSet result;
	rules.getPeriods( fixture.day1Dates_[4], fixture.day1Dates_[5], result);

	BOOST_REQUIRE_EQUAL( 0, result.size());
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testSingleDayWithEliminationBoundary)
{
	// Daily Rules:
	// |-------day1-----------|
	//   1     3      6     8  
	//   |=====|      |=====|
	// Calendar range: 
	//         3      6
	//         |^^^^^^|
	DailyCalendarRule rules;
	DailyCalendarRuleTestFixture fixture;

	rules.mergeDailyPeriod( fixture.day1Times_[1], fixture.day1Times_[3]);
	rules.mergeDailyPeriod( fixture.day1Times_[6], fixture.day1Times_[8]);
	Calendar::PeriodSet result;
	rules.getPeriods( fixture.day1Dates_[3], fixture.day1Dates_[6], result);

	BOOST_REQUIRE_EQUAL( 0, result.size());
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testCrossDayMerging)
{
	// Daily Rules:
	// |-------day1-----------|---------day2--------|---------day3--------|
	// begin 0      8       end     0     8       end     0
	// |=====|      |=========|=====|     |=========|=====|
	// Calendar range: 
	//         3                                              6
	//         |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|
	DailyCalendarRule rules;
	DailyCalendarRuleTestFixture fixture;
	rules.mergeDailyPeriod( fixture.dayBeginTime_, fixture.day1Times_[0]);
	rules.mergeDailyPeriod( fixture.day1Times_[8], fixture.dayEndTime_);

	Calendar::PeriodSet result;
	rules.getPeriods( fixture.day1Dates_[3], fixture.day3Dates_[6], result);
	BOOST_REQUIRE_EQUAL( 2, result.size());

	Calendar::PeriodSet::const_iterator resI = result.begin();
	CHECK_TIME_INTERVAL( fixture.day1Dates_[8], fixture.day2Dates_[0], *resI); ++resI;
	CHECK_TIME_INTERVAL( fixture.day2Dates_[8], fixture.day3Dates_[0], *resI);
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testFullDayRule)
{
	// Daily Rules: begin - end
	DailyCalendarRule rules;
	DailyCalendarRuleTestFixture fixture;
	rules.mergeDailyPeriod( fixture.dayBeginTime_, fixture.dayEndTime_);
	Calendar::PeriodSet result;
	rules.getPeriods( fixture.day1Dates_[3], fixture.day3Dates_[6], result);
	BOOST_REQUIRE_EQUAL( 1, result.size());

	Calendar::PeriodSet::const_iterator resI = result.begin();
	CHECK_TIME_INTERVAL( fixture.day1Dates_[3], fixture.day3Dates_[6], *resI);
}

} // namespace
